{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2a7c070a-ea0a-4ad5-a37c-e55cf4b45bbf",
   "metadata": {},
   "source": [
    "## 查询参数校验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4e536e72-153a-4b08-bb1d-19de1cb57078",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.10.5 (tags/v3.10.5:f377153, Jun  6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "print(sys.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12e1a20e-c2eb-4279-aa19-26cc61fa201a",
   "metadata": {},
   "source": [
    "Python版本是3.10.5。让我们以下面的应用程序为例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "530c0baa-0c94-4cd8-9af4-9c95948e26e6",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [8684]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:62909 - \"GET /items/?q=test HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:62946 - \"GET /items/ HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [8684]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str | None = None):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4262884-d090-409a-9079-250110ebadd0",
   "metadata": {},
   "source": [
    "如果在浏览器中输入 http://127.0.0.1:8009/items/?q=test\n",
    "\n",
    "会看到 {\"items\":[{\"item_id\":\"Foo\"},{\"item_id\":\"Bar\"}],\"q\":\"test\"}\n",
    "\n",
    "查询参数 q 的类型为 str，默认值为 None，因此它是可选的。\n",
    "\n",
    "如果在浏览器中输入 http://127.0.0.1:8009/items/\n",
    "\n",
    "会看到 {\"items\":[{\"item_id\":\"Foo\"},{\"item_id\":\"Bar\"}]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8e62d41-10d1-4853-94bf-ba9fc3ebc04d",
   "metadata": {},
   "source": [
    "## 额外的校验"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa1bdcc1-2f69-470e-b058-18fdf289a1d5",
   "metadata": {},
   "source": [
    "我们打算添加约束条件：即使 q 是可选的，但只要提供了该参数，则该参数值不能超过5个字符的长度。\n",
    "\n",
    "为此，首先从 fastapi 导入 Query：\n",
    "\n",
    "现在，将 Query 用作查询参数的默认值，并将它的 max_length 参数设置为 5："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "626efa25-8d26-4b72-a87b-8e2dc64626b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [8684]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:63000 - \"GET /items/?q=testdata HTTP/1.1\" 422 Unprocessable Entity\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [8684]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Query\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str | None = Query(default=None, max_length=5)):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53b410d7-db9a-40ca-aa9c-7254251640bb",
   "metadata": {},
   "source": [
    "如果在浏览器中输入 http://127.0.0.1:8009/items/?q=testdata\n",
    "\n",
    "会看到 {\"detail\":[{\"type\":\"string_too_long\",\"loc\":[\"query\",\"q\"],\"msg\":\"String should have at most 5 characters\",\"input\":\"testdata\",\"ctx\":{\"max_length\":5},\"url\":\"https://errors.pydantic.dev/2.5/v/string_too_long\"}]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f9fe575-8cbd-4c1f-a47c-97ee07ad7b86",
   "metadata": {},
   "source": [
    "由于我们必须用 Query(default=None) 替换默认值 None，Query 的第一个参数同样也是用于定义默认值。\n",
    "\n",
    "然后，我们可以将更多的参数传递给 Query。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fe6bc01-9c4f-4bee-be61-2f98184a6307",
   "metadata": {},
   "source": [
    "## 添加更多校验"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b0f5b78-c89c-4a1b-93d2-73c5ab4d0974",
   "metadata": {},
   "source": [
    "你还可以添加 min_length 参数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c621dec6-0c76-487b-ab77-3220c86c3591",
   "metadata": {},
   "outputs": [],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Query\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str | None = Query(default=None, min_length=3, max_length=5)):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33622052-d680-4084-a086-8d44c9a177f7",
   "metadata": {},
   "source": [
    "## 添加正则表达式"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ede1200-c420-40e4-98fd-c0ced49983c6",
   "metadata": {},
   "source": [
    "你可以定义一个参数值必须匹配的正则表达式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c03ef5ee-8fe4-4e31-8aee-30367a6ee6ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [8684]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:63087 - \"GET /items/?q=tests HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:63088 - \"GET /items/?q=testa HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:63091 - \"GET /items/?q=testb HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:63093 - \"GET /items/?q=text HTTP/1.1\" 422 Unprocessable Entity\n",
      "INFO:     127.0.0.1:63094 - \"GET /items/?q=testab HTTP/1.1\" 422 Unprocessable Entity\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [8684]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Query\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str | None = Query(default=None, min_length=3, max_length=5, pattern=\"^test.?$\")):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "214c1b49-ad20-4cc5-937b-4f1cc39117f1",
   "metadata": {},
   "source": [
    "这个指定的正则表达式通过以下规则检查接收到的参数值：\r\n",
    "\r\n",
    "^：以该符号之后的字符开头，符号之前没有字符testery: 值精确地teste\n",
    ".?：零个或一个任意字符ry。\r\n",
    "$: 到ery 之后没有更\n",
    "\n",
    "根据上述规则，test、tests、testa、testb、testc、testd都是允许的字符。\n",
    "testab、testabc、text则不是被允许的。多字符。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa67737a-a4b1-4347-9534-1c1d2bcbd93a",
   "metadata": {},
   "source": [
    "## 声明为必需参数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbd70eda-6ad8-4b85-932b-d512a34696b7",
   "metadata": {},
   "source": [
    "当我们不需要声明额外的校验或元数据时，只需不声明默认值就可以使 q 参数成为必需参数，例如：\n",
    "\n",
    "q: str\n",
    "\n",
    "因此，当你在使用 Query 且需要声明一个值是必需的时，只需不声明默认参数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "82b7975e-b728-46ee-8944-80a9d92df785",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [8684]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:63271 - \"GET /items/ HTTP/1.1\" 422 Unprocessable Entity\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [8684]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Query\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str = Query(min_length=3, max_length=5, pattern=\"^test.?$\")):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ac624da-d672-4a16-a331-108bbd9e8082",
   "metadata": {},
   "source": [
    "如果在浏览器中输入 http://127.0.0.1:8009/items/\n",
    "\n",
    "会看到 {\"detail\":[{\"type\":\"missing\",\"loc\":[\"query\",\"q\"],\"msg\":\"Field required\",\"input\":null,\"url\":\"https://errors.pydantic.dev/2.5/v/missing\"}]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b879418-5637-4fb9-ae6c-25eef79e0f3d",
   "metadata": {},
   "source": [
    "## 使用省略号(...)声明必需参数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67874389-bc3f-4b5f-8bd4-29c7fde1361e",
   "metadata": {},
   "source": [
    "有另一种方法可以显式的声明一个值是必需的，即将默认参数的默认值设为 ... ：\n",
    "\n",
    "这将使 FastAPI 知道此查询参数是必需的。\n",
    "\n",
    "请记住，在大多数情况下，当你需要某些东西时，可以简单地省略 default 参数，因此你通常不必使用 ... "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee267e82-881b-4799-b068-6a08cd65125b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI, Query\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/\")\n",
    "async def read_items(q: str = Query(default=..., min_length=3)):\n",
    "    results = {\"items\": [{\"item_id\": \"Foo\"}, {\"item_id\": \"Bar\"}]}\n",
    "    if q:\n",
    "        results.update({\"q\": q})\n",
    "    return results\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e763522-fdd3-4f80-9039-a65e3edaef58",
   "metadata": {},
   "source": [
    "## 声明更多元数据"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4afbf9d5-4100-4a4b-a86a-d2493437d8ab",
   "metadata": {},
   "source": [
    "你可以添加更多有关该参数的信息。\n",
    "你可以添加 title：\n",
    "\n",
    "Query(default=None, title=\"Query string\", min_length=3)\n",
    "\n",
    "以及 description：\n",
    "\n",
    "Query(default=None, title=\"Query string\", description=\"Query string for the items to search in the database\", min_length=3)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
